import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.KeyStroke;
import javax.swing.Timer;

public class EndersGame extends JFrame {

	// TODO Separate generating buggers in each screen with functions of time
	// TODO combine for loop for initializing stuff?
	// TODO more efficiency

	// TODO Get final Widths/Heights
	public static final int WINDOW_WIDTH = 500, WINDOW_HEIGHT = 700,
			TIMER_INCREMENTS_REPAINT = 5, TIMER_INCREMENTS_LEVEL = 10000,
			TIMER_GAME_ACTIONS = 5;
	// TODO get: Fractions determining height given to each component
	public static final double BOARD_HEIGHT = 0.70, MINIMAP_HEIGHT = 0.30;

	public static int dodgedCounter = 0;
	public static int sets = 4; // Number of sets of Humans and Buggers going
								// on
	public static boolean inGame = true; // True indicates the user has not won
											// or lost yet
	static boolean initial = true;
	private static Board board;
	public static ArrayList<Minimap> minimaps = new ArrayList<Minimap>();

	private final String RESTART_GAME = "RESTART_GAME";

	private ArrayList<Human> humans;
	private ArrayList<ArrayList<Bugger>> buggers;

	private Container frameContainer;
	private GridBagConstraints boardConstraints, minimapConstraints;

	private Timer levelTimer, bugGenTimer;
	private Timer gameTimer; // Checks collisions and moves Buggers
	private Timer setSwitchTimer; // Switches the set of Buggers that is being
									// generated

	private int level; // Number that varies the difficulty of the game
	private int currentSetGen; // The current set of Buggers/Human where Buggers
								// are being generated
	private int genCounter; // The number of generated Buggers. Gets reset after
							// a certain amount of generated Buggers

	public EndersGame() {
		level = 1;
		currentSetGen = 0;
		genCounter = 0;

		frameContainer = getContentPane();
		frameContainer.setLayout(new GridBagLayout());

		// Initialize Humans and Buggers
		humans = new ArrayList<Human>();
		buggers = new ArrayList<ArrayList<Bugger>>();
		for (int i = 0; i < sets; i++) {
			humans.add(new Human(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2));
			buggers.add(new ArrayList<Bugger>());
		}

		// Setup the constraints to place the Board onto the frame
		boardConstraints = new GridBagConstraints();
		boardConstraints.fill = GridBagConstraints.BOTH;
		boardConstraints.gridwidth = 4;
		boardConstraints.gridx = 0;
		boardConstraints.gridy = 0;
		boardConstraints.weightx = 1;
		boardConstraints.weighty = BOARD_HEIGHT;

		board = new Board(humans.get(0), buggers.get(0)); // Initialize the
															// board

		frameContainer.add(board, boardConstraints); // Add it to the JFrame

		// Setup the constraints to place the Minimaps onto the frame
		minimapConstraints = new GridBagConstraints();
		minimapConstraints.fill = GridBagConstraints.BOTH;
		minimapConstraints.gridx = 0;
		minimapConstraints.gridy = 1;
		minimapConstraints.weightx = 1;
		minimapConstraints.weighty = MINIMAP_HEIGHT;

		// Initialize Minimaps and add them to the JFrame
		for (int i = 0; i < (sets - 1); i++) {
			Minimap minimap;
			if (i == 0) {
				minimapConstraints.gridx = 0;
				minimap = new Minimap(null, null); // Make the first
													// minimap empty
			} else {
				minimapConstraints.gridx = i;
				minimap = new Minimap(humans.get(i), buggers.get(i));
			}
			minimaps.add(minimap);
			frameContainer.add(minimaps.get(i), minimapConstraints);
		}

		levelTimer = new Timer(TIMER_INCREMENTS_LEVEL, levelController);
		// levelTimer = new Timer(10000, levelController); // TEMP
		gameTimer = new Timer(TIMER_GAME_ACTIONS, gameController);
		bugGenTimer = new Timer(genBugGenDelay(level), bugGenController);
		setSwitchTimer = new Timer(genSetSwitchDelay(level), setSwitchController);

//		startTimers();

		// Setup key bindings
		InputMap inputMap = getRootPane().getInputMap(
				JComponent.WHEN_IN_FOCUSED_WINDOW);
		ActionMap actionMap = getRootPane().getActionMap();

		inputMap.put(KeyStroke.getKeyStroke("ENTER"), RESTART_GAME);

		actionMap.put(RESTART_GAME, new AbstractAction() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (initial) {
					initial = false;
					startTimers();
				}
				
				if (!inGame) {
					reInitialize();
				}
			}
		});

		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
		setLocationRelativeTo(null);
		setTitle("Ender's Last Game");
		setResizable(false);
		setVisible(true);
		validate();
	}

	public static void main(String[] args) {
		new EndersGame();
	}

	private void startTimers() {
		levelTimer.start();
		gameTimer.start();
		bugGenTimer.start();
		setSwitchTimer.start();
	}

	private void stopTimers() {
		levelTimer.stop();
		gameTimer.stop();
		bugGenTimer.stop();
		setSwitchTimer.stop();
	}

	private void reInitialize() {
		inGame = true;

		// Initialize Humans and Buggers
		humans = new ArrayList<Human>();
		buggers = new ArrayList<ArrayList<Bugger>>();
		for (int i = 0; i < sets; i++) {
			humans.add(new Human(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2));
			buggers.add(new ArrayList<Bugger>());
			if (i == 0) {
				board.setHuman(humans.get(i));
				board.setBuggers(buggers.get(i));
				minimaps.get(i).setHuman(null);
				minimaps.get(i).setBuggers(null);
			} else if (i < (sets - 1)) {
				minimaps.get(i).setHuman(humans.get(i));
				minimaps.get(i).setBuggers(buggers.get(i));
			}
		}
		level = 1;
		dodgedCounter = 0;
		currentSetGen = 0;
		genCounter = 0;
		startTimers();
	}

	// Check collisions between Human and Buggers given
	public void checkCollisions(Human human, ArrayList<Bugger> buggers) {
		for (int i = 0; i < buggers.size(); i++) {
			Bugger bugger = buggers.get(i);
			if (human.getBounds().intersects(bugger.getBounds())) {
				// human.setVisible(false); // TEMP COMMENT
				// bugger.setVisible(false); // TEMP COMMENT
				 inGame = false;
				 stopTimers();
			}
		}
	}

	// Insert a bugger at the top of its screen at a random x position
	public void generateBugger(Human human, ArrayList<Bugger> buggers) {
		int velocity, upperLimit, lowerLimit = 1;

		// x coordinate of Bugger spawning point
//		int xSpawn = (int) (Math.random() * (EndersGame.WINDOW_WIDTH - Bugger.IMAGE_WIDTH));
		int xSpawn = genRandNum(human.getX() - 50, human.getX() + 50);

		
		if (level == 1) {
			velocity = 1;
		} else {
			if (level > 4) {
				upperLimit = 4;
				upperLimit = 2;
			} else {
				upperLimit = level;
				if (upperLimit < 3) {
					lowerLimit = 1;
				} else {
					lowerLimit = upperLimit - 2;
				}
			}
			velocity = genRandNum(lowerLimit, upperLimit);
		}

		buggers.add(new Bugger(xSpawn, 0, velocity, genRandNum(0, 1)));

		// System.out.println("Generated bugger at x coordinate: " + xSpawn);
		// TEMP
	}

	// Increase the level as prescribed intervals
	private ActionListener levelController = new ActionListener() {
		public void actionPerformed(ActionEvent ae) {
			if (level < 20) {
				level += 1;
				int delay = (int) (3 * (level ^ 2) + 600);
				bugGenTimer.setDelay(genBugGenDelay(level)); // Decrease the
																// time
				// between
				// Bugger spawns
				System.out.println("Level Timer Delay set to: " + delay);
				// bugGenTimer.setDelay(50); // TEMP
				System.out.println("Level Increased to " + level); // TEMP
			}
		}
	};

	// Call generateBugger at timed intervals and to random screens
	private ActionListener bugGenController = new ActionListener() {
		public void actionPerformed(ActionEvent ae) {
//			System.out.println("Current set gen: " + currentSetGen);
			ArrayList<Bugger> buggerList = buggers.get(currentSetGen);
			Human human = humans.get(currentSetGen);
			generateBugger(human, buggerList);
		}
	};

	// Move buggers, remove them as necessary and check collisions
	private ActionListener gameController = new ActionListener() {
		public void actionPerformed(ActionEvent ae) {
			for (int i = 0; i < buggers.size(); i++) { // Go through the
														// ArrayList<Bugger> of
														// ArrayList<Bugger>
				ArrayList<Bugger> buggerList = buggers.get(i); // Get the
																// current
																// ArrayList<Bugger>
				checkCollisions(humans.get(i), buggerList);
				for (int k = 0; k < buggerList.size(); k++) { // Go through the
																// ArrayList<Bugger>
					Bugger bugger = buggerList.get(k); // Get the current Bugger
					if (bugger.isVisible()) {
						bugger.move();
					} else {
						buggerList.remove(k);
						EndersGame.dodgedCounter += 1;
						// System.out.println("Bugger removed because not visible");
						// // TEMP
					}
				}
			}
		}
	};

	private ActionListener setSwitchController = new ActionListener() {
		public void actionPerformed(ActionEvent e) {
			int delay = genSetSwitchDelay(level);
			if (delay != 0) {
				setSwitchTimer.setDelay(delay);
			}
			System.out.println("Set switch timer delay set to: " + delay); // TEMP

			humans.get(currentSetGen).incoming = false; // Indicate previous set
														// is no longer
														// generating Buggers
			int randomSetGen = (int) (Math.random() * (sets - 1));
			// Generate a random number until it does not match the previous one
			// used
			while (currentSetGen == randomSetGen) {
				randomSetGen = (int) (Math.random() * (sets - 1));
				System.out.println("Finding a random number");
			}
			currentSetGen = randomSetGen;
			humans.get(currentSetGen).incoming = true;
		}
	};

	// Switch the Human and Buggers inside the given Minimap with the Human and
	// Buggers inside the Board
	public static void switchInsides(Minimap minimap) {
		Human minimapHuman = minimap.getHuman();
		Human boardHuman = board.getHuman();
		Human holderHuman = boardHuman;
		ArrayList<Bugger> minimapBuggers = minimap.getBuggers();
		ArrayList<Bugger> boardBuggers = board.getBuggers();
		ArrayList<Bugger> holderBuggers = boardBuggers;

		if (minimapHuman != null && minimapBuggers != null) {
			board.setHuman(minimapHuman);
			minimap.setHuman(holderHuman);
			board.setBuggers(minimapBuggers);
			minimap.setBuggers(holderBuggers);
		}
	}

	// Move the Human and Buggers inside the given Minimap to the Board
	// Move the Human and Buggers inside the Board back to its respective
	// Minimap
	public static void moveInsides(Minimap minimap) {
		Human minimapHuman = minimap.getHuman();
		Human boardHuman = board.getHuman();
		ArrayList<Bugger> minimapBuggers = minimap.getBuggers();
		ArrayList<Bugger> boardBuggers = board.getBuggers();

		if (minimapHuman != null && minimapBuggers != null) {
			// Find the empty minimap and fill it in with what's on the Board
			// now
			for (int i = 0; i < (sets - 1); i++) {
				Minimap currentMap = minimaps.get(i);
				if (currentMap.getHuman() == null
						&& currentMap.getBuggers() == null) {
					currentMap.setHuman(boardHuman);
					currentMap.setBuggers(boardBuggers);
					board.setHuman(minimapHuman);
					board.setBuggers(minimapBuggers);
					minimap.setHuman(null);
					minimap.setBuggers(null);
				}
			}
		}
	}

	// Function for generating the delay for the set switch timer.
	// As x increases, the returned value decreases
	private int genSetSwitchDelay(int x) {
		int toReturn;
		
		int delay = (-500 * (level ^ 2) + 6000);
		
		if (delay > 0) {
			toReturn = delay;
		} else {
			toReturn = 1000;
		}
		
		return toReturn;
	}

	// Function for generating the delay for the set switch timer.
	// As x increases, the returned value decreases
	private int genBugGenDelay(int x) {
		int toReturn;
		int delay = (int) (-10 * (level ^ 2) + 450);
		
		if (delay > 0) {
			toReturn = delay;
		} else {
			toReturn = 200;
		}
		return toReturn;
	}

	// Generate random number between the numbers specified (inclusive)
	private int genRandNum(int lower, int higher) {
		return (int) (Math.random() * (higher - lower + 1) + lower);
	}
}
